查看原文
其他

使用模拟器进行x64驱动的Safengine脱壳+导入表修复

hzqst 看雪学院 2019-05-25

0x00  概述


近日闲的蛋疼发现一款某地外挂被火绒爆勒索,本来准备安排一哈它的勒索功能。

 

结果发现sys直接明文存放在exe里,我也懒得花钱,遂dump之。





IDA看了下发现是Safengine Shielden v2.4.0.0。





0x02  分析


直接加载sys后发现往afd.sys挂了一个注册表回调,回调入口是跳板。



由于驱动直接加载会返回C0000001导致立刻被卸载,无法直接dump。


于是改用模拟器加载并在真实DriverEntry处dump整个sys内存:


经过调试发现Safengine保护的驱动会使用DriverObject->DriverSection进行PsLoadedModuleList的遍历并修复导入表。


如果在模拟器中给上假的DriverSection会导致SE访问到非法内存。




于是我们给模拟器补上自己伪造的PsLoadedModuleList和DriverSection:




DriverObject.DriverSection = (PVOID)m_DriverLdrEntry;


补完后可以正常执行到真实入口点,可以看到这个sys执行的第一次API是RtlInitUnicodeString:



我们定义代码从加壳sys的.sedata/.vmp节执行到.text 或INIT 就算进入真实入口点:

bool bIsUnknownSection = (0 == memcmp((char *)SectionHeader[i].Name, ".text\0\0\0", 8)

|| 0 == memcmp((char *)SectionHeader[i].Name, "INIT\0\0\0\0", 8)) ? false : true;


if(currentModule == ctx->m_ImageBase && ctx->m_IsPacked && !ctx->m_ImageRealEntry)

{

   FakeSection_t *section = NULL;

   if (ctx->FindSectionByAddress(address, &section) && !section->IsUnknownSection)

   {

       ctx->m_ImageRealEntry = address;

   }

}


至此,我们可以完整dump出刚刚进入真实入口点时的加壳驱动。

virtual_buffer_t imagebuf(ctx.m_ImageEnd - ctx.m_ImageBase);



uc_mem_read(uc, ctx.m_ImageBase, imagebuf.GetBuffer(), ctx.m_ImageEnd - ctx.m_ImageBase);



FILE *fp = fopen("dump.sys", "wb");



fwrite(imagebuf.GetBuffer(), ctx.m_ImageEnd - ctx.m_ImageBase, 1, fp);



fclose(fp);


对比加壳sys和dumpsys可以发现 safengine使用了代码自修改。





从区段自带可写入属性就可以看出这一点。



当然,直接dump的sys是没法脱IDA的,还需要修正一下节表(文件和内存中的RVA完全一致)。

auto SectionCount = ntheader->FileHeader.NumberOfSections;

for (USHORT i = 0; i < SectionCount; ++i)

{

   SectionHeader[i].PointerToRawData = SectionHeader[i].VirtualAddress;

   SectionHeader[i].SizeOfRawData = SectionHeader[i].Misc.VirtualSize;

}


以及修正入口点RVA到我们刚才用模拟器跑出的真正入口点。

ntheader->OptionalHeader.AddressOfEntryPoint = (ULONG)(ctx.m_ImageRealEntry - ctx.m_ImageBase);




这样入口点就能被IDA正确识别了。


然后还需要修复导入表,由于没法用工具修复,我们只能自己手撸代码解决。



在可执行节中搜索所有的FF15 FF25(call qword ptr[xxx]和jmp qword ptr [xxx])。

using namespace blackbone;



PatternSearch patternFF15("\xFF\x15");

PatternSearch patternFF25("\xFF\x25");


auto SectionCount = ntheader->FileHeader.NumberOfSections;



for (USHORT i = 0; i < SectionCount; ++i)

{

   if (SectionHeader[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE))

   {

       patternFF15.Search((PUCHAR)ImageBase + SectionHeader[i].VirtualAddress,

           SectionHeader[i].Misc.VirtualSize, out);



       patternFF25.Search((PUCHAR)ImageBase + SectionHeader[i].VirtualAddress,

           SectionHeader[i].Misc.VirtualSize, out);

   }

}


对搜索到的所有 FF 15 xx xx xx xx 和FF 25 xx xx xx xx 判断后四字节是否是真正的RVA,并且根据RVA算出api地址,看api地址是否是有效API。

for (size_t j = 0; j < out.size(); ++j)

   {

       int insn_rva = (int)(out[j] - (ptr_t)ImageBase);

       int call_rva = insn_rva + *(int *)(out[j] + 2) + 6;

       if (call_rva >= 0 && call_rva < (int)ImageSize)

       {

           auto call_api_addr = *(ULONG_PTR *)(call_rva + (PUCHAR)ImageBase);



           FakeAPI_t *api_ptr = NULL;

           if (FindAPIByAddress(call_api_addr, dllnamew, &api_ptr))

           {

std::cout << "found IAT 0x" << std::hex << insn_rva << " to " << api_ptr->ProcedureName << "\n";

//....




保存这些insn_rva和API名,利用这些数据重建导入表,新加一个.idata节存放导入表。





memset(&SectionHeader[SectionCount], 0, sizeof(IMAGE_SECTION_HEADER));

   SectionHeader[SectionCount].VirtualAddress = SectionHeader[SectionCount].PointerToRawData = NewIATRva;

   SectionHeader[SectionCount].SizeOfRawData = (DWORD)RebuildIATSize;

   SectionHeader[SectionCount].Misc.VirtualSize = PAGE_ALIGN(RebuildIATSize);

   SectionHeader[SectionCount].Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE;

   memcpy(SectionHeader[SectionCount].Name, ".idata\0\0", 8);

   ntheader->FileHeader.NumberOfSections++;



   ntheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = RebuildIATRva;

   ntheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = (DWORD)RebuildIATDescSize;

   ntheader->OptionalHeader.SizeOfImage += (DWORD)PAGE_ALIGN(RebuildIATSize);



fwrite(RebuildIATBuffer.GetBuffer(), RebuildIATBuffer.GetLength(), 1, fp);


并将新增的节追加到dump的PE文件尾部,修改导入表入口,成功重建部分导入表。



至此,该驱动的Safengine脱壳+导入表修复完美完成。



0x03  总结


用Safengine加壳驱动并且不vm不混淆任何函数是非常不安全的行为,在模拟器的安排下几乎等同于裸奔。(此时有效的保护只有代码重组,只能干扰一下IDA的F5,如果单看汇编就是裸奔)


用VMProtect 3.X加壳并保护导入表会稍微好一点,不过稍微修改一下本文中的导入表重建算法,像https://bbs.pediy.com/thread-248812.htm一样 识别出VMP的API call,也可以无压力完美脱壳。


样本和脱完壳的sys下载地址已放附件。(点击阅读原文即可获取)



0x04  相关项目


模拟器:

Unicorn PE (https://github.com/hzqst/unicorn_pe)




- End -


看雪ID:hzqst             

https://bbs.pediy.com/user-619065.htm


本文由看雪论坛 hzqst 原创

转载请注明来自看雪社区



热门图书推荐:

立即购买!



征题正在火热进行中!

(晋级赛Q1即将于3月10日开启,敬请期待!)




热门文章阅读


1、FPS网络游戏自动瞄准漏洞分析以及实现

2、Linux0.11共享内存机制

3、记一次蔓灵花APT组织针对巴基斯坦定向攻击的样本分析

4、CVE-2017-7533 漏洞利用



热门课程推荐














公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com


点击下方“阅读原文”

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存